#include "OoO/frontend.h"
#include "config.h"

namespace config
{
    const Value<uint64_t> frontend_width = {"frontend_width", 2, "frontend width"};
    const Value<uint64_t> frontend_ibuf_size = {"frontend_ibuf_size", 32, "frontend ibuf size"};
    const Value<uint64_t> frontend_latency = {"frontend_latency", 2, "frontend latnecy"};
    const Value<uint64_t> frontend_reset_pc = {"frontend_reset_pc", 0x80000000, "frontend reset pc"};
    const Value<uint64_t> frontend_ras_size = {"frontend_ras_size", 32, "frontend ras size"};
    const Value<uint64_t> frontend_btb_size = {"frontend_btb_size", 128, "frontend btb size"};

} // namespace config

NormalFrontend::NormalFrontend() : ibuf(config::frontend_ibuf_size), bpu(config::frontend_btb_size, config::frontend_ras_size),
                             ras_miss("ras-miss"), ras_hit("ras-hit"), bimodal_miss("bimodal_miss"), bimodal_hit("bimodal_hit"),
                             btb_miss("btb_miss"), btb_hit("btb_hit"), total_miss("total_miss"), total_hit("total_hit"),
                             unpredict_miss("unpredict_miss"), unpredict_hit("unpredict_hit"),
                             error_predict_miss("error_predict_miss"),
                             ibuf_full("ibuf-full")
{
    reset();
}
NormalFrontend::~NormalFrontend()
{
}
void NormalFrontend::run()
{
    size_t ibuf_size = ibuf.size();
    SPDLOG_TRACE("[Frontend] ibuf_size {} state.wrong_op_num {}", ibuf_size, state.wrong_op_num);
    bool could_fetch = ibuf_size + state.wrong_op_num + config::frontend_width <= config::frontend_ibuf_size;
    if (could_fetch)
    {
        for (uint64_t i = 0; i < config::frontend_width; i++)
        {
            /* Branch Predict */
            BPU_Res bpu_res = bpu.predict(state.pc);
            word_t pnpc = state.pc + 4;
            if (bpu_res.taken && bpu_res.valid)
            {
                pnpc = bpu_res.target;
            }
            if (!state.wrong_path)
            {
                auto op = read_op();
                if (op->valid == false)
                {
                    ibuf.push({op, false, {0}, cycle});
                    cycle++;
                    return;
                }
                if (op->trace.pc != state.pc)
                {
                    print_status();
                    print_counter();
                    SPDLOG_ERROR("[Frontend] read op {} in wrong path.", op->id);
                }

                word_t dnpc = op->trace.branch_taken ? op->trace.branch_pc : (op->trace.pc + 4);
                bool bpred_hit = dnpc == pnpc;
                if (op->trace.opcode == OP_CBR || op->trace.opcode == OP_JMP)
                {
                    if (bpred_hit)
                    {
                        total_hit++;
                    }
                    else
                    {
                        total_miss++;
                    }
                }
                if (bpred_hit)
                {
                    if (bpu_res.valid)
                    {
                        switch (bpu_res.type)
                        {
                        case BranchType::Jump:
                        case BranchType::Call:
                            btb_hit++;
                            SPDLOG_TRACE("[BranchPredict] BTB hit pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                            break;
                        case BranchType::Branch:
                            bimodal_hit++;
                            SPDLOG_TRACE("[BranchPredict] bimodal hit pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                            break;
                        case BranchType::Ret:
                        case BranchType::CallRet:
                            ras_hit++;
                            SPDLOG_TRACE("[BranchPredict] ras hit pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                            break;
                        default:
                            SPDLOG_ERROR("[BranchPredict] should not make NONE branch instruction into bpu");
                            break;
                        }
                    }
                    else if (op->trace.opcode == OP_CBR || op->trace.opcode == OP_JMP)
                    {
                        unpredict_hit++;
                    }
                }
                else
                {
                    state.wrong_path = true;
                    state.correct_pc = dnpc;
                    if (bpu_res.valid)
                    {
                        switch (bpu_res.type)
                        {
                        case BranchType::Jump:
                        case BranchType::Call:
                            btb_miss++;
                            SPDLOG_TRACE("[BranchPredict] BTB miss pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                            break;
                        case BranchType::Branch:
                            bimodal_miss++;
                            SPDLOG_TRACE("[BranchPredict] bimodal miss pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                            break;
                        case BranchType::Ret:
                        case BranchType::CallRet:
                            ras_miss++;
                            SPDLOG_TRACE("[BranchPredict] ras miss pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                            break;
                        default:
                            SPDLOG_ERROR("[BranchPredict] should not make NONE branch instruction into bpu");
                            break;
                        }
                    }
                    else if (op->trace.opcode == OP_CBR || op->trace.opcode == OP_JMP)
                    {
                        unpredict_miss++;
                        SPDLOG_TRACE("[BranchPredict] un-predict miss pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                    }
                    else
                    {
                        error_predict_miss++;
                        SPDLOG_TRACE("[BranchPredict] error-predict miss pc:{:08x} dnpc:{:08x} pnpc{:08x}", state.pc, dnpc, pnpc);
                    }
                }
                ibuf.push({op, !bpred_hit, bpu_res, cycle});
            }
            else
            {
                state.wrong_op_num++;
            }
            state.pc = pnpc;
        }
    }
    else
    {
        ibuf_full++;
    }

    cycle++;
}

void NormalFrontend::update(word_t addr, BPU_Res correct_res)
{
    bpu.update(addr, correct_res);
}

void NormalFrontend::redirect(word_t addr, BPU_Res correct_res)
{
    update(addr, correct_res);
    state.pc = correct_res.taken ? correct_res.target : addr + 4;
    state.wrong_path = false;
    state.wrong_op_num = 0;
    if (state.correct_pc != state.pc)
    {
        SPDLOG_ERROR("[Frontend] redirect target not correct!");
        return;
    }
}

void NormalFrontend::reset()
{
    ibuf.clear();

    bpu.reset();

    state = {0};
    state.pc = config::frontend_reset_pc;
    state.fetching_ops.clear();
}
void NormalFrontend::print_counter()
{
    float total_hit_rate = 100.0f * total_hit / (total_hit + total_miss);
    float ras_hit_rate = 100.0f * ras_hit / (ras_hit + ras_miss);
    float bimodal_hit_rate = 100.0f * bimodal_hit / (bimodal_hit + bimodal_miss);
    float btb_hit_rate = 100.0f * btb_hit / (btb_hit + btb_miss);
    printf(COLOR("[Frontend] ", GREEN) "Branch total_hit_rate " COLOR("%.02f%%", BLUE) " ras_hit_rate " COLOR("%.02f%%", BLUE) " bimodal_hit_rate " COLOR("%.02f%%", BLUE) " btb_hit_rate " COLOR("%.02f%%", BLUE) "\n",
           total_hit_rate, ras_hit_rate, bimodal_hit_rate, btb_hit_rate);
}
void NormalFrontend::print_status()
{
}

FrontendOP NormalFrontend::fetch_op()
{
    FrontendOP fop = {0};
    if (ibuf.empty())
    {
        return fop;
    }
    SPDLOG_TRACE("[Frontend] try to fetch op.");
    auto &ibuf_entry = ibuf.front();
    bool can_fetch = ibuf_entry.cycle + config::frontend_latency <= cycle;
    if (can_fetch)
    {
        fop.op = ibuf_entry.op;
        fop.bpu_res = ibuf_entry.bpu_res;
        fop.bpred_miss = ibuf_entry.bpred_miss;
        ibuf.pop();
        SPDLOG_TRACE("[Frontend] fetch op:{} success!", fop.op->id);
    }
    else
    {
        SPDLOG_TRACE("[Frontend] fetch op failed ready cycle {}, now {}. ", ibuf_entry.cycle + config::frontend_latency, cycle);
    }
    return fop;
}
